package com.nineclock.attendance.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.nineclock.attendance.dto.AttendGroupDTO;
import com.nineclock.attendance.dto.AttendPunchDTO;
import com.nineclock.attendance.dto.AttendPunchUserWholeDayDTO;
import com.nineclock.attendance.dto.AttendsDTO;
import com.nineclock.attendance.enums.AttendEnums;
import com.nineclock.attendance.mapper.AttendPunchMapper;
import com.nineclock.attendance.pojo.AttendPunch;
import com.nineclock.attendance.service.AttendGroupService;
import com.nineclock.attendance.service.AttendPunchService;
import com.nineclock.attendance.utils.PunchUtils;
import com.nineclock.common.enums.ResponseEnum;
import com.nineclock.common.exception.NcException;
import com.nineclock.common.threadlocal.CurrentUserHolder;
import com.nineclock.common.utils.BeanHelper;
import com.nineclock.common.utils.DateTimeUtil;
import com.nineclock.system.dto.SysCompanyUserDTO;
import com.nineclock.system.feign.SysOrganizationFeign;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 黑马程序员
 * @Company http://www.ithiema.com
 * @Version 1.0
 */
@Service
@Slf4j
public class AttendPunchServiceImpl implements AttendPunchService {

    @Autowired
    AttendPunchMapper attendPunchMapper;

    @Autowired
    AttendGroupService attendGroupService;


    @Override//打卡
    public void punch(AttendPunchDTO attendPunchDTO) {
        if(attendPunchDTO == null){
            throw new NcException(ResponseEnum.INVALID_PARAM_ERROR);
        }
        //复制  DTO为 pojo
        AttendPunch attendPunch = BeanHelper.copyProperties(attendPunchDTO, AttendPunch.class);

        //查询该员工对应的 考勤组 对象
        AttendGroupDTO attendGroupDTO = attendGroupService.getAttendGroupByUserId();

        //设置属性值

        //是否在有效区域范围内
        boolean validatePunchArea = PunchUtils.validatePunchArea(attendPunchDTO, attendGroupDTO);
        if(!validatePunchArea){
            throw new NcException(ResponseEnum.PUNCH_INVALID_AREA);
        }
        attendPunch.setAreaValid(validatePunchArea);

        //打卡的时间 日期字符串 "yyyy-MM-dd"
        attendPunch.setPunchDateStr(DateUtil.format(new Date(),  "yyyy-MM-dd"));
        attendPunch.setPunchTime(new Date());

        //设置 上下午打卡
        attendPunch.setPunchOnOffWork(DateTimeUtil.noonType(new Date()) + 1);


        //校验是否工作日打卡
        boolean validateWorkDay = PunchUtils.validateWorkDay(attendGroupDTO);
        if(!validateWorkDay){
            throw new NcException(ResponseEnum.PUNCH_INVALID_DAY);
        }

        //是否有效打卡
        boolean validatePunch = this.validatePunch(attendPunch);
        if(validatePunch){
            //设置 上、下 午打卡类型
            PunchUtils.setPunchType(attendPunch, attendGroupDTO);
        }



        attendPunch.setCompanyId(CurrentUserHolder.get().getCompanyId());
        attendPunch.setCompanyUserId(CurrentUserHolder.get().getCompanyUserId());
        attendPunch.setCreateTime(new Date());

        //保存打卡记录到数据库
        attendPunchMapper.insert(attendPunch);
    }

    @Override//根据系统用户ID获取全天的打卡数据
    public AttendPunchUserWholeDayDTO queryPunchRecord() {

        //查询上午的打卡情况
        //查询当前登录的员工 当天的 上午的 有效 打卡情况
        LambdaQueryWrapper<AttendPunch>  attendPunchQw1 = new LambdaQueryWrapper<>();
        //用户和企业信息
        attendPunchQw1.eq(AttendPunch::getCompanyUserId, CurrentUserHolder.get().getCompanyUserId());
        attendPunchQw1.eq(AttendPunch::getCompanyId, CurrentUserHolder.get().getCompanyId());
        //当天
        attendPunchQw1.eq(AttendPunch::getPunchDateStr, DateUtil.format(new Date(), "yyyy-MM-dd"));
        //上午
        attendPunchQw1.eq(AttendPunch::getPunchOnOffWork , AttendEnums.NOON_TYPE_MORNING.value());
        //是否有效打卡
        attendPunchQw1.eq(AttendPunch::getEffectiveValid, true);

        AttendPunch morningAttendPunch = attendPunchMapper.selectOne(attendPunchQw1);



        //查询下午的打卡情况
        //查询当前登录的员工 当天的 下午的 有效 打卡情况
        LambdaQueryWrapper<AttendPunch>  attendPunchQw2 = new LambdaQueryWrapper<>();
        //用户和企业信息
        attendPunchQw2.eq(AttendPunch::getCompanyUserId, CurrentUserHolder.get().getCompanyUserId());
        attendPunchQw2.eq(AttendPunch::getCompanyId, CurrentUserHolder.get().getCompanyId());
        //当天
        attendPunchQw2.eq(AttendPunch::getPunchDateStr, DateUtil.format(new Date(), "yyyy-MM-dd"));
        //上午
        attendPunchQw2.eq(AttendPunch::getPunchOnOffWork , AttendEnums.NOON_TYPE_AFTERNOON.value());
        //是否有效打卡
        attendPunchQw2.eq(AttendPunch::getEffectiveValid, true);

        AttendPunch afterNoonAttendPunch = attendPunchMapper.selectOne(attendPunchQw2);


        AttendPunchUserWholeDayDTO wholeDayDTO = new AttendPunchUserWholeDayDTO();
        if(morningAttendPunch != null){
            wholeDayDTO.setAttendPunchMorningResponse(BeanHelper.copyProperties(morningAttendPunch, AttendPunchDTO.class));
        }

        if(afterNoonAttendPunch != null){
            wholeDayDTO.setAttendPunchAfterNoonResponse(BeanHelper.copyProperties(afterNoonAttendPunch, AttendPunchDTO.class));
        }

        return wholeDayDTO;
    }

    /**
     * 获取 时间段内的 日期集合
     * @param startTime
     * @param endTime
     * @return
     */
    public List<String> getSpanDateList(String startTime ,String endTime){
        //获取 时间 段内的每一天 日期 集合
        List<Date> dateList = DateTimeUtil.getDatesBetweenTwoDate(startTime, endTime, "yyyy-MM-dd");
        //转换为  字符串日期格式的集合
        List<String> dateStrList = dateList.stream().map(date -> {
            return DateUtil.format(date, "yyyy-MM-dd");
        }).collect(Collectors.toList());

        return dateStrList;
    }
    @Autowired
    SysOrganizationFeign organizationFeign;

    /**
     * 1. 查询当前企业下所有的员工信息
     *
     * 2. 查询每个员工该时间范围内所有的考勤情况
     *
     * 3. 封装每个员工的考勤情况 到集合中
     * @param startTime  yyyy-MM-dd  2021-06-01  2021-06-01 09:00:00
     * @param endTime  yyyy-MM-dd
     * @return
     */
    @Override
    public List<AttendPunchDTO> queryMembers(String startTime, String endTime) {

        List<String> dateStrList = this.getSpanDateList(startTime, endTime);

        //查询该企业下的所有的员工信息
        List<SysCompanyUserDTO> companyUserDTOList = organizationFeign.queryAllCompanyUser().getData();
        //查询每个员工该时间范围内所有的考勤情况
        List<AttendPunchDTO> attendPunchDTOList = this.getAttendPunch(companyUserDTOList, startTime, endTime, dateStrList);


        return attendPunchDTOList;
    }

    /**
     * 获取 指定员工 的时间段内考勤情况
     * @param companyUserDTOList
     * @param startTime
     * @param endTime
     * @param dateStrList
     * @return
     */
    public List<AttendPunchDTO> getAttendPunch(List<SysCompanyUserDTO> companyUserDTOList , String startTime ,String endTime, List<String> dateStrList){
        List<AttendPunchDTO> attendPunchDTOList = new ArrayList<>();
        if(CollectionUtil.isNotEmpty(companyUserDTOList)){
            //循环每个员工
            for (SysCompanyUserDTO companyUserDTO : companyUserDTOList) {
                AttendPunchDTO attendPunchDTO = BeanHelper.copyProperties(companyUserDTO, AttendPunchDTO.class);

                //查询每个员工该时间范围内所有的考勤情况
                LambdaQueryWrapper<AttendPunch> attendPunchQw = new LambdaQueryWrapper<>();
                attendPunchQw.eq(AttendPunch::getCompanyId, companyUserDTO.getCompanyId());
                attendPunchQw.eq(AttendPunch::getCompanyUserId, companyUserDTO.getId());
                attendPunchQw.between(AttendPunch::getPunchDateStr , startTime, endTime);//时间有效范围内
                attendPunchQw.eq(AttendPunch::getEffectiveValid, true); //有效打卡
                //每个员工 时间段内 的有效打卡记录
                List<AttendPunch> attendPunchList = attendPunchMapper.selectList(attendPunchQw);

                //根据日期 对考勤记录 分组
                Map<String, List<AttendPunch>> attendPunchMap = attendPunchList.stream().collect(Collectors.groupingBy(AttendPunch::getPunchDateStr));
                //存储每个员工每天的打卡情况
                List<AttendsDTO> attendsList = new ArrayList<>();

                for (String date : dateStrList) {
                    //每天对应一个 AttendsDto对象
                    AttendsDTO attendsDTO = new AttendsDTO();

                    //员工 某一天对应的考勤情况
                    List<AttendPunch> attendPunches = attendPunchMap.get(date);
                    //获取当天的考勤状态
                    String attendStatus = this.handleAttendStatus(attendPunches);
                    //设置日期和状态
                    attendsDTO.setAttendDate(date);
                    attendsDTO.setAttendStatus(attendStatus);
                    //添加到 集合中
                    attendsList.add(attendsDTO);
                }
                //把员工的考勤情况 放到 考勤对象中
                attendPunchDTO.setAttendsList(attendsList);

                attendPunchDTOList.add(attendPunchDTO);
            }
        }
        return attendPunchDTOList;
    }


    @Override
    public void exportAttendData(String startTime, String endTime) throws IOException {
        //根据 日期 查询出指定 时间段内 的 考勤情况
        List<AttendPunchDTO> attendPunchDTOList = this.queryMembers(startTime, endTime);

        //设置响应
        RequestAttributes requestAttributes =
                RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        //获取响应对象
        HttpServletResponse response = servletRequestAttributes.getResponse();
        response.setHeader("Content-disposition", "attachment;filename=Attendance.xls");
        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setCharacterEncoding("utf-8");


        EasyExcel.write(response.getOutputStream())
                .registerWriteHandler(styleStrategy())
                .head(head(attendPunchDTOList))
                .registerWriteHandler(new MyColumnStyle())
                .sheet("第一个工作表")
                .doWrite(data(attendPunchDTOList)); //执行写操作

    }

    @Override
    public String generateExcelReportByCompanyId(Long companyId) {
        //根据企业获取员工集合
        List<SysCompanyUserDTO> companyUserDTOS = organizationFeign.queryAllUserByCompanyId(companyId).getData();
        //获取 本月 第一天和最后一天
        String firstDay = DateTimeUtil.getCurrentMonthFirstDay(); // 2021-06-01
        String lastDay = DateTimeUtil.getCurrentMonthLastDay();
        // 获取这一个时间段的 日期集合
        List<String> dateStrList = this.getSpanDateList(firstDay, lastDay);

        //获取 时间段内员工的考勤情况
        List<AttendPunchDTO> attendPunchDTOList = this.getAttendPunch(companyUserDTOS, firstDay, lastDay, dateStrList);

        //把数据 保存到本地， 获取excel的地址
        //4. 生成Excel
        String foldername = "D:/考勤数据/"+companyId;
        String filename = foldername +"/"+companyUserDTOS.get(0).getCompanyName()+"-"+firstDay.substring(0,7)+"-考勤数据.xlsx"; //2021-02-05

        if(!new File(foldername).exists()){
            new File(foldername).mkdirs();
        }

        EasyExcel.write(filename)
                .excelType(ExcelTypeEnum.XLSX)
                .head(head(attendPunchDTOList))
                .registerWriteHandler(new MyColumnStyle())
                .registerWriteHandler(styleStrategy())
                .sheet("考勤数据")
                .doWrite(data(attendPunchDTOList));

        return filename;
    }

    /**
     * 操作 excel 的表头
     * @return
     */
    public List<List<String>>  head(List<AttendPunchDTO> attendPunchDTOList){
        List<List<String>> head = new ArrayList<>();
        head.add(Arrays.asList("姓名"));
        head.add(Arrays.asList("工号"));
        head.add(Arrays.asList("部门"));
        head.add(Arrays.asList("职位"));

        //添加 日期表头
        if(CollectionUtil.isNotEmpty(attendPunchDTOList)){
            //获取 第一个员工的考勤 情况
            AttendPunchDTO attendPunchDTO = attendPunchDTOList.get(0);
            //获取指定时间段的考勤情况
            List<AttendsDTO> attendsList = attendPunchDTO.getAttendsList();
            if(CollectionUtil.isNotEmpty(attendsList)){
                for (AttendsDTO attendsDTO : attendsList) {
                    head.add(Arrays.asList( attendsDTO.getAttendDate()));
                }
            }
        }
        return head;
    }

    /**
     * 操作excel的数据
     * @return
     */
    public List<List<String>>  data(List<AttendPunchDTO> attendPunchDTOList){
        List<List<String>> data = new ArrayList<>();
        if(CollectionUtil.isNotEmpty(attendPunchDTOList)){
            //循环每个员工的考勤情况
            for (AttendPunchDTO attendPunchDTO : attendPunchDTOList) {
                //对应每个员工的考勤（对应一行数据）
                List<String> list = new ArrayList<>();
                list.add(attendPunchDTO.getUserName());
                list.add(attendPunchDTO.getWorkNumber()); //工号
                list.add(attendPunchDTO.getDepartmentName());
                list.add(attendPunchDTO.getPost()); //职位
                //每个员工的考勤情况
                List<AttendsDTO> attendsList = attendPunchDTO.getAttendsList();
                if(CollectionUtil.isNotEmpty(attendsList)){
                    for (AttendsDTO attendsDTO : attendsList) {
                        list.add(attendsDTO.getAttendStatus());
                    }
                }

                data.add(list);
            }

        }

        return data;
    }

    public  HorizontalCellStyleStrategy styleStrategy(){
        //写入excel
        WriteCellStyle cellStyle = new WriteCellStyle();
        //表头和内容都是水平居中
        cellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);

        //设置字体
        WriteFont font = new WriteFont();
        font.setFontName("宋体");
        font.setFontHeightInPoints((short)12);
        cellStyle.setWriteFont(font);

        HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(cellStyle,cellStyle);
        return styleStrategy;
    }




    private String handleAttendStatus(List<AttendPunch> punches) {
        String amAttendStatus = "缺卡";//上午 - 考勤
        String pmAttendStatus = "缺卡";//下午 - 考勤
        if(CollectionUtil.isNotEmpty(punches)){
            for (AttendPunch punch : punches) {
                if(punch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_MORNING.value()){
                    amAttendStatus = this.getPushDesc(punch.getMorningPunchType());
                }else if (punch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_AFTERNOON.value()){
                    pmAttendStatus = this.getPushDesc(punch.getAfternoonPunchType());
                }
            }
        }
        return amAttendStatus+"/"+pmAttendStatus;
    }

    private String getPushDesc(Integer morningPunchType) {
        String desc = "";
        switch (morningPunchType){
            case 1:
                desc = AttendEnums.PUNCH_TYPE_OK.toString();
                break;
            case 2:
                desc = AttendEnums.PUNCH_TYPE_LATE.toString();
                break;
            case 3:
                desc = AttendEnums.PUNCH_TYPE_EARLY.toString();
                break;
            case 4:
                desc = AttendEnums.PUNCH_TYPE_STAYALAY.toString();
                break;
            case 5:
                desc = AttendEnums.PUNCH_TYPE_NOT_WORK_DAY.toString();
                break;
            default:
                break;
        }
        return desc;
    }

    /**
     * 1. 查询打卡记录，如果没有查询到，无论何时打卡都是有效的
     * 2. 查询到了
     *  如果是上午打卡， 则无效
     *  如果是下午打卡， 以最后一次打卡记录为准, 之前打卡记录无效
     * @param attendPunch
     * @return
     */
    private boolean validatePunch(AttendPunch attendPunch) {
        LambdaQueryWrapper<AttendPunch> punchQw = new LambdaQueryWrapper<>();

        punchQw.eq(AttendPunch::getPunchOnOffWork, attendPunch.getPunchOnOffWork());
        punchQw.eq(AttendPunch::getCompanyId, CurrentUserHolder.get().getCompanyId());
        punchQw.eq(AttendPunch::getCompanyUserId, CurrentUserHolder.get().getCompanyUserId());
        punchQw.eq(AttendPunch::getPunchDateStr , attendPunch.getPunchDateStr());

        Integer count = attendPunchMapper.selectCount(punchQw);

        if(count == 0){
            attendPunch.setEffectiveValid(true);
        }else{
            if(attendPunch.getPunchOnOffWork() == 1){
                attendPunch.setEffectiveValid(false);
            }else{
                attendPunch.setEffectiveValid(true);

                AttendPunch updateAttendPunch = new AttendPunch();
                updateAttendPunch.setEffectiveValid(false);
                attendPunchMapper.update(updateAttendPunch, punchQw);
            }
        }

        return attendPunch.getEffectiveValid();
    }


    class MyColumnStyle extends AbstractColumnWidthStyleStrategy {
        protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList,
                                      Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
            writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), 4500);
        }
    }
}
